package com.lipaluma.field.mapper.multiple.impl;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import com.lipaluma.annotations.validations.ValidateCollection;
import com.lipaluma.context.Context;
import com.lipaluma.exceptions.CsvParserValidationException;
import com.lipaluma.field.mapper.FieldValidatedOnEachValue;
import com.lipaluma.field.mapper.mapping.FieldMappingInfo;
import com.lipaluma.field.mapper.mapping.MappingOptions;
import com.lipaluma.field.mapper.multiple.AbstractStandardMultipleFieldMapper;
import com.lipaluma.field.validator.ChainValidator;
import com.lipaluma.field.validator.ValidationBasicOptions;
import com.lipaluma.field.validator.ValidationError;
import com.lipaluma.field.validator.Validator;
import com.lipaluma.field.validator.ValidatorFactory;
import com.lipaluma.util.FormatterUtils;
import com.lipaluma.util.ParserUtils;
import com.lipaluma.util.ReflectionUtils;
import com.lipaluma.xml.mapping.XmlFieldMapping;

public class AggregateMultipleFieldMapper extends AbstractStandardMultipleFieldMapper implements FieldValidatedOnEachValue {
	private Object target;
	private String pattern;
	private ChainValidator chainValidatorOnEachValue;
	
	public AggregateMultipleFieldMapper() {}
	public AggregateMultipleFieldMapper(List<FieldMappingInfo> mappings) {
		this(mappings, null, false);
	}
	public AggregateMultipleFieldMapper(List<FieldMappingInfo> mappings, String pattern) {
		this(mappings, pattern, false);
	}
	public AggregateMultipleFieldMapper(List<FieldMappingInfo> mappings, boolean keepAll) {
		this(mappings, null, keepAll);
	}
	public AggregateMultipleFieldMapper(List<FieldMappingInfo> mappings, String pattern, boolean keepAll) {
		super(mappings);
		this.pattern = pattern;
		this.keepAll = keepAll;
	}

	@Override
	public void addNewValidatorOnEachValue(Validator validator) {
		if(validator != null)
			this.chainValidatorOnEachValue.add(validator);
	}
	
	@Override
	public void init(Context context, Field field) {
		super.init(context, field);
		initValidatorOnEachValue(context, field);
	}

	@Override
	public void init(Context context, Field field, XmlFieldMapping mapping) {
		super.init(context, field, mapping);
		initValidatorOnEachValue(context, field, mapping);
	}
	@Override
	protected void init(MappingOptions options) {
		pattern = options.getPattern();
		keepAll = options.isKeepAll();
	}

	@Override
	protected ChainValidator createChainValidator(Context context, Field field) {
		ValidationBasicOptions validationOptions = ValidationBasicOptions.builder().byAnnotation(field.getAnnotation(ValidateCollection.class)).build();
		return ValidatorFactory.createNewChainValidator(context, field, validationOptions);
	}

	@Override
	protected ChainValidator createChainValidator(Context context, Field field, XmlFieldMapping mapping) {
		return ValidatorFactory.createNewChainValidator(context, field, mapping);
	}
	
	@Override
	public void initValidatorOnEachValue(Context context, Field field) {
		Class<? extends Annotation> clazz = ValidatorFactory.getValidationAnnotationByType(ReflectionUtils.getParameterizedClass(field));
		ValidationBasicOptions validationOptions = ValidationBasicOptions.builder().byAnnotation(field.getAnnotation(clazz)).build();
		this.chainValidatorOnEachValue = ValidatorFactory.createNewChainValidatorOnEachValue(context, field, validationOptions);
	}

	@Override
	public void initValidatorOnEachValue(Context context, Field field, XmlFieldMapping mapping) {
		this.chainValidatorOnEachValue = ValidatorFactory.createNewChainValidatorOnEachValue(context, field, mapping);
	}
	
	@Override
	public void eachValueIsRequired(boolean required) {
		chainValidatorOnEachValue.setRequired(required);
	}
	
	@Override
	protected Object parseExtractedValues(List<String> values) {
		Object result = extractValueFromObject(target);

		if(field.getType().isArray()) {
			Class<?> componentType = field.getType().getComponentType();
			result = Array.newInstance(field.getType().getComponentType(), values.size());
			for (int i=0; i<values.size(); i++) {
				Object valueParsed = ParserUtils.parseToTypeOrEnum(componentType, values.get(i), pattern);
				Array.set(result, i, valueParsed);
			}
			return result;
		} else {
			if(result == null)
				result = ReflectionUtils.instantiateCollection(field.getType());
			Class<?> componentType = ReflectionUtils.getParameterizedClass(field);
			for (int i=0; i<values.size(); i++) {
				Object valueParsed = ParserUtils.parseToTypeOrEnum(componentType, values.get(i), pattern);
				((Collection<Object>)result).add(valueParsed);
			}			
			return result;
		}
	}

	@Override
	public void validateValue(Object value) throws CsvParserValidationException {
		if(value.getClass().isArray())
			validateEachValues(Arrays.asList((Object[])value));
		else
			validateEachValues(new ArrayList<Object>((Collection<Object>)value));
		super.validateValue(value);
	}

	@Override
	public void validateEachValues(List<Object> values) throws CsvParserValidationException {
		if(chainValidatorOnEachValue != null) {
			chainValidatorOnEachValue.reset();
			List<ValidationError> errors = new ArrayList<ValidationError>();
			for (Object object : values) {
				chainValidatorOnEachValue.validate(object, errors);
			}
			if(!errors.isEmpty()) {
				StringBuilder message = new StringBuilder(getDescription());
				message.append("An error occured on validation of the value ").append(values).append("\n");
				for (ValidationError error : errors) {
					message.append(error.getMessage()).append("\n");
				}
				throw new CsvParserValidationException(message.toString());
			}
		}
	}

	@Override
	protected List<String> formatValue(Object value) {
		List<Object> values;
		if(value.getClass().isArray())
			values = Arrays.asList((Object[])value);
		else
			values = new ArrayList<Object>((Collection<Object>)value);
		List<String> result = new ArrayList<String>();
		for (Object object : values) {
			String valueFormatted = FormatterUtils.formatFromTypeOrEnum(object, pattern);
			result.add(valueFormatted);
		}
		return result;
	}
	@Override
	public String getDescription() {
		return "";
	}
}
